package com.sjtu.vfact.textual.controller;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JSpinner;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableColumn;

import com.sjtu.vfact.MainController;
import com.sjtu.vfact.exception.NoDocumentLoadedException;
import com.sjtu.vfact.exception.NoIntersectInQuickSummarizerException;
import com.sjtu.vfact.exception.NoRowSelectedException;
import com.sjtu.vfact.textual.algorithm.Facta;
import com.sjtu.vfact.textual.algorithm.Facta_A;
import com.sjtu.vfact.textual.algorithm.Facta_B;
import com.sjtu.vfact.textual.algorithm.Facta_C;
import com.sjtu.vfact.textual.algorithm.Facta_D;
import com.sjtu.vfact.textual.model.Document;
import com.sjtu.vfact.textual.model.FACTAModel;
import com.sjtu.vfact.textual.model.MasterCollection;
import com.sjtu.vfact.textual.model.MasterTermset;
import com.sjtu.vfact.textual.model.TermSet;
import com.sjtu.vfact.textual.tablemodel.DetailSummarizerTableModel;
import com.sjtu.vfact.textual.tablemodel.MasterCollectionTableModel;
import com.sjtu.vfact.textual.tablemodel.QuickSummarizerTableModel;
import com.sjtu.vfact.textual.tablemodel.UncoveredTermsTableModel;
import com.sjtu.vfact.textual.view.FACTAFrame;


public class MiningController {
	
	private MainController main;
	
	private FACTAFrame frame;
	private FACTAModel model;
	private MasterCollectionTableModel masterCollectionTableModel;
	private QuickSummarizerTableModel quickSumTableModel;
	private DetailSummarizerTableModel detailSumTableModel;
	private UncoveredTermsTableModel uncoveredTermsTableModel;
	
	private Facta_A facta_A;
	private Facta_B facta_B;
	private Facta_C facta_C;
	private Facta_D facta_D;
	
	private boolean isFactaD;
	
	/**
	 * constructor
	 */
	public MiningController(MainController main){
		this.main = main;
		this.masterCollectionTableModel = new MasterCollectionTableModel();
		this.quickSumTableModel = new QuickSummarizerTableModel();
		this.detailSumTableModel = new DetailSummarizerTableModel();
		this.uncoveredTermsTableModel = new UncoveredTermsTableModel();
		
		isFactaD = false;
	}
	
	/**
	 * initialize 
	 */
	public void start(){
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					frame = new FACTAFrame();
					frame.displayFrame();
					frame.setTableMasterCollectionModelAndSorter(masterCollectionTableModel);
					frame.setTableQuickSumModelAndSorter(quickSumTableModel);
					frame.setTableDetailSumModelAndSorter(detailSumTableModel);
					frame.setTableUncoveredTermsModelAndSorter(uncoveredTermsTableModel);
					
					setTableMasterCollectionColumnsWidth();
					setTableQuickSumColumnsWidth();
					setTableDetailSumColumnsWidth();
					setTableUncoveredTermsColumnsWidth();
					
					addListeners();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
	
	/**
	 * add listeners to the corresponding components
	 */
	private void addListeners(){
		frame.addBtnFactaAListener(new BtnFactaAListener());
		frame.addBtnFactaBListener(new BtnFactaBListener());
		frame.addBtnFactaCListener(new BtnFactaCListener());
		frame.addBtnFactaDListener(new BtnFactaDListener());
		frame.addBtnOpenFileListener(new BtnOpenFileListener());
		frame.addBtnQuickSumListener(new BtnQuickSumListener());
		frame.addBtnDetailSumListener(new BtnDetailSumListener());
		frame.addBtnShowGraphListener(new BtnShowGraphListener());
		frame.addBtnRunSSAListener(new BtnRunSSAListener());
		frame.addBtnShowSSAGraphListener(new BtnShowSSAGraphListener());
		
		frame.addTableMasterCollectionListener(new TableMasterCollectionListener());
		
		frame.addSpinnerCSDListener(new SpinnerCSDListener());
	}
	
	/**
	 * class implements ActionListener for Button FACTA-A
	 * @author budiyanto
	 *
	 */
	class BtnFactaAListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			try{
				if(model == null){
					throw new NoDocumentLoadedException();
				}
				
				facta_A = new Facta_A(model);
				facta_A.addPropertyChangeListener(new FactaListener());
				
				int lengthMin = frame.getLengthMin();
				int lengthMax = frame.getLengthMax();
				int numberN = frame.getNumberN();
				
				frame.setCollectingProgress(0);
				frame.clearTxtAreaCollecting();
				frame.setEnabledSpinnerCSD(true);
				
				model.setMasterCollection(runFacta_A(model.getDocuments(), lengthMin, lengthMax, numberN));
				masterCollectionTableModel.initializeTableModel(model.getMasterCollection());
				
			} catch (NoDocumentLoadedException e1){
				e1.printError(frame);
			}
	
		}
		
	}
	
	/**
	 * class implements ActionListener for Button FACTA-B
	 * @author budiyanto
	 *
	 */
	class BtnFactaBListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			try{
				if(model == null){
					throw new NoDocumentLoadedException();
				}
				
				facta_B = new Facta_B(model);
				facta_B.addPropertyChangeListener(new FactaListener());
				
				int lengthMin = frame.getLengthMin();
				int lengthMax = frame.getLengthMax();
				int numberN = frame.getNumberN();
				int numberP = frame.getP();
				
				frame.setCollectingProgress(0);
				frame.clearTxtAreaCollecting();
				frame.setEnabledSpinnerCSD(true);

				model.setMasterCollection(runFacta_B(model.getDocuments(), lengthMin, lengthMax, numberN, numberP));
				masterCollectionTableModel.initializeTableModel(model.getMasterCollection());
			} catch(NoDocumentLoadedException e1){
				e1.printError(frame);
			}
		}
		
	}
	
	/**
	 * class implements ActionListener for Button FACTA-C
	 * @author budiyanto
	 *
	 */
	class BtnFactaCListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e){
			try{
				if(model == null){
					throw new NoDocumentLoadedException();
				}
				
				facta_C = new Facta_C(model);
				facta_C.addPropertyChangeListener(new FactaListener());
				
				int lengthMin = frame.getLengthMin();
				int lengthMax = frame.getLengthMax();
				int numberN = frame.getNumberN();
				int numberP = frame.getP();
				
				frame.setCollectingProgress(0);
				frame.clearTxtAreaCollecting();
				frame.setEnabledSpinnerCSD(true);

				model.setMasterCollection(runFacta_C(model.getDocuments(), lengthMin, lengthMax, numberN, numberP));
				masterCollectionTableModel.initializeTableModel(model.getMasterCollection());
				
			} catch(NoDocumentLoadedException e1){
				e1.printError(frame);
			}
		}
		
	}
	
	/**
	 * class implements ActionListener for Button FACTA-D
	 * @author budiyanto
	 *
	 */
	class BtnFactaDListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			try{
				if(model == null){
					throw new NoDocumentLoadedException();
				}
				
				facta_D = new Facta_D(model);
				facta_D.addPropertyChangeListener(new FactaListener());
				
				int lengthMin = frame.getLengthMin();
				int lengthMax = frame.getLengthMax();
				int numberN = frame.getNumberN();
				int numberP = frame.getP();
				
				frame.setCollectingProgress(0);
				frame.clearTxtAreaCollecting();
				frame.setEnabledSpinnerCSD(true);
				
				isFactaD = true;
				
				model.setMasterCollection(runFacta_D(model.getDocuments(), lengthMin, lengthMax, numberN, numberP));
				masterCollectionTableModel.initializeTableModel(model.getMasterCollection());
				
				isFactaD = false;
			} catch(NoDocumentLoadedException e1){
				e1.printError(frame);
			}
		}
		
	}
	
	/**
	 * class implements ActionListener for Button Open File
	 * @author budiyanto
	 *
	 */
	class BtnOpenFileListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			JFileChooser chooser = new JFileChooser();
			int returnVal = chooser.showOpenDialog(frame);
			String status = "";
			if(returnVal == JFileChooser.APPROVE_OPTION){
				File file = chooser.getSelectedFile();
				frame.setTxtFldFileName(file.getAbsolutePath());
				
				model = new FACTAModel();
								
				int totalDocs = model.loadDocument(file);
				
				status = totalDocs + " documents are loaded from the file";
				frame.setLblStatus(status);
				
				masterCollectionTableModel.clearTable();
				
				int documentSize = model.getDocumentsSize();
				frame.setSpinnerCSDValue(1, documentSize);
				
//				model.printDocuments();
			}else{
				status = "Open command canceled by user";
				frame.setLblStatus(status);
			}
		}
		
	}
	
	/**
	 * class implements ActionListener for Button Quick Summarizer
	 * @author budiyanto
	 *
	 */
	class BtnQuickSumListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			try{
				int[] selectedRows = frame.getMasterCollectionSelectedRows();
				
				if(selectedRows.length <= 0){
					throw new NoRowSelectedException();
				}
				
				TermSet termset = model.runQuickSummarizer(model.getTermsetFromCollection(selectedRows));
				
				if(termset.isEmpty()){ //no intersection
					throw new NoIntersectInQuickSummarizerException();
				}
					
				quickSumTableModel.initializeTableModel(termset, model.getDocIDsFromTermset(termset));
				
			} catch(NoRowSelectedException e1){
				e1.printError(frame);
			} catch(NoIntersectInQuickSummarizerException e2){
				e2.printError(frame);
			}
						
		}
		
	}
	
	/**
	 * class implements ActionListener for Button Detail Summarizer
	 * @author budiyanto
	 *
	 */
	class BtnDetailSumListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			try{
				
				int[] selectedRows = frame.getMasterCollectionSelectedRows();
				
				if(selectedRows.length <= 0){
					throw new NoRowSelectedException();
				}
				
				int lengthMin = frame.getLengthMin();
				int lengthMax = frame.getLengthMax();
				int numberN = frame.getNumberN();
				
				List<TermSet> termsets = model.getTermsetFromCollection(selectedRows);
				MasterCollection collection = runDetailSum(termsets, lengthMin, lengthMax, numberN);
				
				Object[] tuple = null;
				
//				int no = 1;
				for(MasterTermset mts : collection.getMasterTermsetList()){
					tuple = new Object[detailSumTableModel.getColumnCount()];
					TermSet ts = mts.getTermset();
					Set<Integer> docIds = model.getDocIDsFromTermset(ts);
//					tuple[EnumeratorTableModel.COL_NO] = no;
					tuple[DetailSummarizerTableModel.COL_SUMMARY] = ts;
					tuple[DetailSummarizerTableModel.COL_DOCIDS] = docIds;
					tuple[DetailSummarizerTableModel.COL_SUPPORT] = docIds.size();
//					no++;
					detailSumTableModel.addRow(tuple);

				}
								
			} catch(NoRowSelectedException e1){
				e1.printError(frame);
			}
			
		}
		
	}
	
	class BtnShowGraphListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			
			main.getGraphController().start(model.getMasterCollection().getMasterTermsetList());
		
		}
		
	}
	
	class BtnRunSSAListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			//model.runSSA(termsets, ranges);
			
			//List<Pair<Integer, TermSet>> termsets = Util.convertDocListToTermsetList(model.getDocuments());
			
			model.runSSA(model.getDocuments(), frame.getSSARanges());
			
			frame.setSSARangeModel(new DefaultComboBoxModel(model.getSSARanges()));
			
			String title = "SSA finished";
			String message = "SSA is finished!!";
			JOptionPane.showMessageDialog(frame, message, title, JOptionPane.INFORMATION_MESSAGE);
			
		}
		
	}
	
	class BtnShowSSAGraphListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			int selectedRangeIndex = frame.getSelectedSSARangeIndex();
			Map<TermSet, Set<Document>> ssaDict = model.getSSADict(selectedRangeIndex);
			main.getGraphController().startSSAGraph(ssaDict);
		}
		
	}
	
	class SpinnerCSDListener implements ChangeListener{

		@Override
		public void stateChanged(ChangeEvent e) {
			JSpinner spinner = (JSpinner) e.getSource();
			int support = (Integer) spinner.getValue();
			
			if(model.getMasterCollection().getSize() > 0){
				int coverage = (int) (model.calculateCoverage(support) * 100);
				int specificity = (int) (model.calculateSpecificity(support) * 100);
				int density = (int) (model.calculateDensity() * 100);
				
				frame.setCoverageValue(coverage);
				frame.setSpecificityValue(specificity);
				frame.setDensityValue(density);
				
				uncoveredTermsTableModel.initializeTableModel(model.getUncoveredTerms(support));
			}
			
			
		}
		
	}
	
	class TableMasterCollectionListener implements TableModelListener{

		@Override
		public void tableChanged(TableModelEvent e) {
			
		}
		
	}
	
	class FactaListener implements PropertyChangeListener{
		
		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			if(Facta.PROGRESS.equals(evt.getPropertyName())){
				
				int progress = (Integer) evt.getNewValue();
				frame.setCollectingProgress(progress);
				
			} else if(Facta.DOCUMENT.equals(evt.getPropertyName())){
				int document = (Integer) evt.getNewValue();
				int documentSize = model.getDocumentsSize() - 1;
				if(isFactaD){
					documentSize = model.getDocumentsSize();
				}
				
				String text = String.format("Finished %d of %d documents.\n", document, documentSize);
				frame.appendTxtAreaCollecting(text);
			}
	
		}
		
	}
	
	/**
	 * run the algorithm FACTA-A
	 * @param L_min
	 * @param L_max
	 * @param N
	 */
	public MasterCollection runFacta_A(List<Document> documents, int L_min, int L_max, int N){
		MasterCollection collection = null;
		try {
			Facta_A_Worker worker = new Facta_A_Worker(documents, L_min, L_max, N);
			worker.execute();
			collection = worker.get();
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		return collection;
	}
	
	/**
	 * Swingworker used to do the intensive computing from FACTA-A algorithm
	 * @author budiyanto
	 *
	 */
	class Facta_A_Worker extends SwingWorker<MasterCollection, Void>{
		
		List<Document> documents;
		int L_min, L_max, N;
		
		Facta_A_Worker(List<Document> documents, int L_min, int L_max, int N){
			this.documents = documents;
			this.L_min = L_min;
			this.L_max = L_max;
			this.N = N;
		}
		
		@Override
		protected MasterCollection doInBackground() throws Exception {
			facta_A.reset();
			return facta_A.run(documents, L_min, L_max, N, -1);			
		}
		
	}
	
	/**
	 * run the algorithm FACTA-B
	 * @param L_min
	 * @param L_max
	 * @param N
	 * @param p
	 */
	public MasterCollection runFacta_B(List<Document> documents, int L_min, int L_max, int N, int p){
		MasterCollection collection = null;
		try {
			Facta_B_Worker worker = new Facta_B_Worker(documents, L_min, L_max, N, p);
			worker.execute();
			collection = worker.get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		return collection;
	}
	
	/**
	 * Swingworker used to do the intensive computing from FACTA-B algorithm
	 * @author budiyanto
	 *
	 */
	class Facta_B_Worker extends SwingWorker<MasterCollection, Void>{
		
		List<Document> documents;
		int L_min, L_max, N, p;
		
		Facta_B_Worker(List<Document> documents, int L_min, int L_max, int N, int p){
			this.documents = documents;
			this.L_min = L_min;
			this.L_max = L_max;
			this.N = N;
			this.p = p;
		}
		
		@Override
		protected MasterCollection doInBackground() throws Exception {
			facta_B.reset();
			return facta_B.run(documents, L_min, L_max, N, p);			
		}
		
	}
	
	/**
	 * run the algorithm FACTA-C
	 * @param L_min
	 * @param L_max
	 * @param N
	 * @param p
	 */
	public MasterCollection runFacta_C(List<Document> documents, int L_min, int L_max, int N, int p){
		MasterCollection collection = null;
		try {
			Facta_C_Worker worker = new Facta_C_Worker(documents, L_min, L_max, N, p);
			worker.execute();
			collection = worker.get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		return collection;
	}
	
	/**
	 * Swingworker used to do the intensive computing from FACTA-C algorithm
	 * @author budiyanto
	 *
	 */
	class Facta_C_Worker extends SwingWorker<MasterCollection, Void>{
		
		List<Document> documents;
		int L_min, L_max, N, p;
		
		Facta_C_Worker(List<Document> documents, int L_min, int L_max, int N, int p){
			this.documents = documents;
			this.L_min = L_min;
			this.L_max = L_max;
			this.N = N;
			this.p = p;
		}
		
		@Override
		protected MasterCollection doInBackground() throws Exception {
			facta_C.reset();
			return facta_C.run(documents, L_min, L_max, N, p);			
		}
		
	}
	
	/**
	 * run the algorithm FACTA-D
	 * @param L_min
	 * @param L_max
	 * @param N
	 * @param p
	 */
	public MasterCollection runFacta_D(List<Document> documents, int L_min, int L_max, int N, int p){
		MasterCollection collection = null;
		try {
			Facta_D_Worker worker = new Facta_D_Worker(documents, L_min, L_max, N, p);
			worker.execute();
			collection = worker.get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		return collection;
	}
	
	/**
	 * Swingworker used to do the intensive computing from FACTA-D algorithm
	 * @author budiyanto
	 *
	 */
	class Facta_D_Worker extends SwingWorker<MasterCollection, Void>{
		
		List<Document> documents;
		int L_min, L_max, N, p;
		
		Facta_D_Worker(List<Document> documents, int L_min, int L_max, int N, int p){
			this.documents = documents;
			this.L_min = L_min;
			this.L_max = L_max;
			this.N = N;
			this.p = p;
		}
		
		@Override
		protected MasterCollection doInBackground() throws Exception {
			facta_D.reset();
			return facta_D.run(documents, L_min, L_max, N, p);			
		}
		
	}
	
	/**
	 * run the Enumerator or detailed summarizer
	 * one of After Mining Task
	 */
	public MasterCollection runDetailSum(List<TermSet> termsets, int L_min, int L_max, int N){
		List<Document> docs = new ArrayList<Document>();
		int id = 1;
		for(TermSet ts : termsets){
			String[] terms = (String[]) ts.toArray();
			Document doc = new Document(id, terms);
			docs.add(doc);
			id++;
		}
		return runFacta_A(docs, L_min, L_max, N);
		
	}
	
	public void setTableMasterCollectionColumnsWidth(){
		TableColumn column = null;
		for(int i = 0; i < masterCollectionTableModel.getColumnCount(); i++){
			column = frame.getTableMasterCollectionColumn(i);
			switch (i) {
			case MasterCollectionTableModel.COL_TERMSET:
				column.setPreferredWidth(MasterCollectionTableModel.COL_TERMSET_WIDTH);
				break;
			case MasterCollectionTableModel.COL_SUPPORT:
				column.setPreferredWidth(MasterCollectionTableModel.COL_SUPPORT_WIDTH);
				break;
			case MasterCollectionTableModel.COL_DOCPAIRS:
				column.setPreferredWidth(MasterCollectionTableModel.COL_DOCPAIRS_WIDTH);
				break;
			default:
				break;
			}
		}
	}
	
	public void setTableUncoveredTermsColumnsWidth(){
		TableColumn column = null;
		for(int i = 0; i < uncoveredTermsTableModel.getColumnCount(); i++){
			column = frame.getTableUncoveredTermsColumn(i);
			switch (i) {
//			case UncoveredTermsTableModel.COL_NO:
//				column.setPreferredWidth(UncoveredTermsTableModel.COL_NO_WIDTH);
//				break;
			case UncoveredTermsTableModel.COL_UNCOVERED_TERMS:
				column.setPreferredWidth(UncoveredTermsTableModel.COL_UNCOVERED_TERMS_WIDTH);
				break;
			case UncoveredTermsTableModel.COL_SUPPORT:
				column.setPreferredWidth(UncoveredTermsTableModel.COL_SUPPORT_WIDTH);
				break;
			case UncoveredTermsTableModel.COL_DOCIDS:
				column.setPreferredWidth(UncoveredTermsTableModel.COL_DOCIDS_WIDTH);
				break;
			default:
				break;
			}
		}
	}
	
	public void setTableQuickSumColumnsWidth(){
		TableColumn column = null;
		for(int i = 0; i < quickSumTableModel.getColumnCount(); i++){
			column = frame.getTableQuickSumColumn(i);
			switch (i) {
//			case SummarizerTableModel.COL_NO:
//				column.setPreferredWidth(SummarizerTableModel.COL_NO_WIDTH);
//				break;
			case QuickSummarizerTableModel.COL_SUMMARY:
				column.setPreferredWidth(QuickSummarizerTableModel.COL_SUMMARY_WIDTH);
				break;
			case QuickSummarizerTableModel.COL_SUPPORT:
				column.setPreferredWidth(QuickSummarizerTableModel.COL_SUPPORT_WIDTH);
				break;
			case QuickSummarizerTableModel.COL_DOCIDS:
				column.setPreferredWidth(QuickSummarizerTableModel.COL_DOCIDS_WIDTH);
				break;
			default:
				break;
			}
		}
	}
	
	public void setTableDetailSumColumnsWidth(){
		TableColumn column = null;
		for(int i = 0; i < detailSumTableModel.getColumnCount(); i++){
			column = frame.getTableDetailSumColumn(i);
			switch (i) {
//			case EnumeratorTableModel.COL_NO:
//				column.setPreferredWidth(EnumeratorTableModel.COL_NO_WIDTH);
//				break;
			case DetailSummarizerTableModel.COL_SUMMARY:
				column.setPreferredWidth(DetailSummarizerTableModel.COL_SUMMARY_WIDTH);
				break;
			case DetailSummarizerTableModel.COL_SUPPORT:
				column.setPreferredWidth(DetailSummarizerTableModel.COL_SUPPORT_WIDTH);
				break;
			case DetailSummarizerTableModel.COL_DOCIDS:
				column.setPreferredWidth(DetailSummarizerTableModel.COL_DOCIDS_WIDTH);
				break;
			default:
				break;
			}
		}
	}
 
}
